﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace FA.Library.Util
{
    /// <summary>
    /// Represents a thread-safe first-in first-out collection of tasks which are executed by
    /// the provided delegate.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class TaskQueue<T> : IDisposable
    {
        private Queue<T> _queue;
        private Action<T> _executeTaskDelegate;
        
        // Threading and synchronisation objects
        private readonly object _queueLock;
        private readonly EventWaitHandle _waitHandle;
        private Thread _thread;

        /// <summary>
        /// Constructor. Provide the delegate which will be invoked when a task is dequeued.
        /// </summary>
        /// <param name="executeTaskDelegate"></param>
        public TaskQueue(Action<T> executeTaskDelegate)
        {
            _executeTaskDelegate = executeTaskDelegate;
            _queue = new Queue<T>();

            _queueLock = new object();
            _waitHandle = new AutoResetEvent(false);
            _thread = new Thread(run);

            _thread.Start();
        }

        /// <summary>
        /// Add an item to the queue.
        /// </summary>
        /// <param name="item"></param>
        public void EnqueueItem(T item)
        {
            lock (_queueLock)
                _queue.Enqueue(item);
            _waitHandle.Set();
        }

        /// <summary>
        /// Dispose the queue. This will run off the remaining commands before disposing.
        /// </summary>
        public void Dispose()
        {
            // Signal the queue to end
            EnqueueItem(default(T));

            // Wait for the queue to end
            _thread.Join();
            _waitHandle.Close();
        }

        /// <summary>
        /// The execution loop of the queue.
        /// </summary>
        private void run()
        {
            while (true)
            {
                T task = default(T);
                lock (_queueLock)
                {
                    if (_queue.Count > 0)
                    {
                        task = _queue.Dequeue();
                        
                        // A null or 'default' item is used to indicate the queue is being disposed so break
                        if (task == null || task.Equals(default(T)))
                            break;
                    }
                }

                if (task != null && !task.Equals(default(T)))
                {
                    // Perform the task
                    _executeTaskDelegate.Invoke(task);
                }
                else
                {
                    // Wait for a new task to be enqueued
                    _waitHandle.WaitOne();
                }
            }
        }
    }
}